package emulator.shell.GUI;

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.KeyEvent;
import java.util.Observable;
import java.util.Observer;

import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JMenuItem;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.JTextPane;
import javax.swing.KeyStroke;
import javax.swing.text.SimpleAttributeSet;

import emulator.EmulatorException;
import emulator.GUI.PersistentFrame;
import emulator.assembler.Assembler;
import emulator.hardware.CPU;
import emulator.hardware.bus.Bus;
import emulator.hardware.clock.Clock;
import emulator.hardware.io.Via6522;
import emulator.hardware.io.peripherals.iec.IecSim;
import emulator.hardware.io.porthistory.PortStateHistory;
import emulator.hardware.memory.MemoryBlockInterface;
import emulator.shell.DebugShell;
import emulator.shell.GUI.menu.*;
import emulator.util.events.EventAggregator;

public class ConsoleFrame extends Observable implements ActionListener, Observer, LogPanel {
	private CPU cpu;
	private MemoryBlockInterface memory;
	private Bus bus;
	private Assembler assembler = new Assembler();
	private IecSim iec_sim;
	private Via6522 via1 = null;
	private Via6522 via2 = null;
	
	private JTextPane content = null;
	private JFrame frame = null;
	private JTextField input_field = null;
	private WindowData window_data = null;
	private JMenuItem menu_item_go;
	private JMenuItem menu_item_halt;
	private String appName;

	private PortStateHistory portStateHistory1;
	private PortStateHistory portStateHistory2;
	private Clock _clock;

	public ConsoleFrame(String appName)
	{
		this.appName = appName;
		window_data = new WindowData(appName, this);
	}

	/* (non-Javadoc)
	 * @see emulator.shell.GUI.LogPanel#write(java.lang.String, javax.swing.text.SimpleAttributeSet)
	 */
	@Override
	public void write(String text, SimpleAttributeSet attributes)
	{
        javax.swing.SwingUtilities.invokeLater(new ConsoleWriteEvent(this, text, attributes));
	}
	
    private JMenuBar createMenuBar()
    {
		JMenuBar menu_bar = new JMenuBar();
		
		createFileMenu(menu_bar);
		createEditMenu(menu_bar);
		createCommandMenu(menu_bar);
		createWindowMenu(menu_bar);
		createHelpMenu(menu_bar);
		
		return menu_bar;
	}

	private static JMenu createMenu(JMenuBar menu_bar, String caption, int shortcut) {
		JMenu menu = new JMenu(caption);
		menu.setMnemonic(shortcut);
		menu_bar.add(menu);
		return menu;
	}

	private static void addEditMenu(JMenu menu, String caption, String image_name,
			int key, ActionListener listener) {
		JMenuItem edit_menu = new JMenuItem(caption, new ImageIcon(image_name));
		edit_menu.setAccelerator(
			KeyStroke.getKeyStroke(
		        key, ActionEvent.CTRL_MASK));
		if (listener != null)
			edit_menu.addActionListener(listener);
		menu.add(edit_menu);
	}

	private static JMenuItem addMenuItem(JMenu menu, String caption, ActionListener listener) {
		JMenuItem menu_item = new JMenuItem(caption);
		menu_item.addActionListener(listener);
		menu.add(menu_item);
		return menu_item;
	}

	private static JMenuItem addMenuItem(JMenu menu, String caption, String icon, ActionListener listener) {
		JMenuItem menu_item = new JMenuItem(caption, new ImageIcon(icon));
		menu_item.addActionListener(listener);
		menu.add(menu_item);
		return menu_item;
	}

	private void createHelpMenu(JMenuBar menu_bar) {
		JMenu menu = createMenu(menu_bar, "Help", KeyEvent.VK_H);
		addMenuItem(menu, "About...", new HelpAbout());
	}

	private void createWindowMenu(JMenuBar menu_bar) {
		JMenu menu = createMenu(menu_bar, "Window", KeyEvent.VK_W);
		window_data.addWindowMenu(menu, "Memory", WindowData.WINDOW_ID_MEMORY);
		window_data.addWindowMenu(menu, "Configuration", WindowData.WINDOW_ID_CONFIGURATION);
		window_data.addWindowMenu(menu, "Debug", WindowData.WINDOW_ID_DEBUG);
		window_data.addWindowMenu(menu, "IEC Log", WindowData.WINDOW_ID_IECLOG);
		window_data.addWindowMenu(menu, "VIA 1", WindowData.WINDOW_ID_VIA1);
		window_data.addWindowMenu(menu, "VIA 2", WindowData.WINDOW_ID_VIA2);
		window_data.addWindowMenu(menu, "Settings", WindowData.WINDOW_ID_SETTINGS);
	}

	private void createCommandMenu(JMenuBar menu_bar)
	{
		JMenu menu = createMenu(menu_bar, "CPU", KeyEvent.VK_C);
		menu_item_go = addMenuItem(menu, "Go", "images/run_icon.png", new ShellCommand("go"));
		menu_item_halt = addMenuItem(menu, "Halt", "images/stop_icon.png", new ShellCommand("halt"));
		addMenuItem(menu, "Reset", new ShellCommand("reset"));
		
		getCpu().addRunningStateObserver(this);
	}

	private void createEditMenu(JMenuBar menu_bar)
	{
		JMenu menu = createMenu(menu_bar, "Edit", KeyEvent.VK_E);
		addEditMenu(menu, "Cut", "images/cut_icon.png", KeyEvent.VK_X, new EditCut(this));
		addEditMenu(menu, "Copy", "images/copy_icon.png", KeyEvent.VK_C, new EditCopy(this));
		addEditMenu(menu, "Paste", "images/paste_icon.png", KeyEvent.VK_V, new EditPaste(this));
	}

	private void createFileMenu(JMenuBar menu_bar)
	{
		JMenu menu = createMenu(menu_bar, "File", KeyEvent.VK_F);
		addMenuItem(menu, "Load...", "images/file_load.png", new FileOpen(this));
		addMenuItem(menu, "Save...", "images/file_save.png", new FileSave(this));
		menu.addSeparator();
		addMenuItem(menu, "Exit", new FileExit(window_data));
	}

    private void addComponentsToPane(Container pane)
    {
        if (!(pane.getLayout() instanceof BorderLayout)) {
            pane.add(new JLabel("Container doesn't use BorderLayout!"));
            return;
        }
        
        JLabel label = new JLabel("Debug Shell");
        pane.add(label, BorderLayout.PAGE_START);
        
	    content = new JTextPane();
	    content.setEditable( false );
	    content.setPreferredSize( new Dimension( 300, 300 ) );
	    pane.add(new JScrollPane(content), BorderLayout.CENTER);
        
	    input_field = new JTextField();
	    input_field.addActionListener(this);
	    pane.add(input_field, BorderLayout.PAGE_END);
    }
    
    /**
     * Create the GUI and show it.  For thread safety,
     * this method should be invoked from the
     * event dispatch thread.
     */
    void createAndShowGUI() {
        //Create and set up the window.
        frame = new PersistentFrame("Console", appName);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        
        // Set Menu Bar
        frame.setJMenuBar(createMenuBar());

        // Set up the content pane.
        addComponentsToPane(frame.getContentPane());
        
        // Use the content pane's default BorderLayout. No need for
        // setLayout(new BorderLayout());
        // Display the window.
        ((PersistentFrame)frame).restorePosition();
        
        frame.setVisible(true);
        
        window_data.restoreWindowStates();
        
        try {
        	getCpu().reset();
        } catch (EmulatorException e) {
        	DebugShell.getInstance().getErr().println("EmulatorException: "+e.getMessage());
        }
    }

	public static ConsoleFrame createConsole(String appName, CPU cpu, MemoryBlockInterface memory, Bus bus) {
    	ConsoleFrame console = new ConsoleFrame(appName);
    	console.setCpu(cpu);
    	console.setMemory(memory);
    	console.setBus(bus);
        javax.swing.SwingUtilities.invokeLater(new ConsoleCreateEvent(console));
        return console;
    }

	/* (non-Javadoc)
	 * @see emulator.shell.GUI.LogPanel#getContentFrame()
	 */
	@Override
	public JTextPane getContentFrame() {
		return content;
	}

	@Override
	public void actionPerformed(ActionEvent event) {
		String input = input_field.getText();
		input_field.selectAll();
		setChanged();
		notifyObservers(input);
	}

	public void editCut()
	{
		input_field.cut();
	}
	
	public void editCopy()
	{
		input_field.copy();
	}
	
	public void editPaste()
	{
		input_field.paste();
	}

	public JFrame getFrame() {
		return frame;
	}

	@Override
	public void update(Observable state, Object run_state) {
		updateRunMenu((Boolean)run_state);
	}

	private void updateRunMenu(boolean run_state)
	{
		menu_item_go.setEnabled(!run_state);
		menu_item_halt.setEnabled(run_state);
	}

	/**
	 * @return the cpu
	 */
	public CPU getCpu() {
		return cpu;
	}

	/**
	 * @param cpu the cpu to set
	 */
	public void setCpu(CPU cpu) {
		this.cpu = cpu;
	}

	/**
	 * @return the memory
	 */
	public MemoryBlockInterface getMemory() {
		return memory;
	}

	/**
	 * @param memory the memory to set
	 */
	public void setMemory(MemoryBlockInterface memory) {
		this.memory = memory;
	}

	/**
	 * @return the bus
	 */
	public Bus getBus() {
		return bus;
	}

	/**
	 * @param bus the bus to set
	 */
	public void setBus(Bus bus) {
		this.bus = bus;
	}

	public void setAssembler(Assembler assembler) {
		this.assembler = assembler;
	}
	
	public Assembler getAssembler() {
		return assembler;
	}

	public void setIecSim(IecSim iec_sim) {
		this.iec_sim = iec_sim; 
	}
	
	public IecSim getIecSim() {
		return iec_sim;
	}

	public void setVias(Via6522 via1, Via6522 via2)
	{
		if (via1 != null)
			this.via1 = via1;
		if (via2 != null)
			this.via2 = via2;
	}
	
	public Via6522 getVia1() {
		return via1;
	}

	public Via6522 getVia2() {
		return via2;
	}

	public void initPortStateHistory(EventAggregator eventAggregator)
	{
		portStateHistory1 = new PortStateHistory(via1, eventAggregator);
		portStateHistory2 = new PortStateHistory(via2, eventAggregator);
	}
	
	public PortStateHistory getPortStateHistory1()
	{
		return portStateHistory1;
	}

	public PortStateHistory getPortStateHistory2()
	{
		return portStateHistory2;
	}

	public void setClock(Clock clock)
	{
		_clock = clock;
	}
	
	public Clock getClock()
	{
		return _clock;
	}
}
